#ifndef RAD_MAIN_H
#define RAD_MAIN_H


//#include <freeradius-devel/ident.h>
//RCSID("$Id$")
extern "C"
{
#include <freeradius-devel/ident.h>
RCSID("$Id$")
#define operator operators
#include <freeradius-devel/libradius.h>
#include <freeradius-devel/conf.h>
#include <freeradius-devel/radpaths.h>
}
int fr_packet_list_id_alloc_by_socket(fr_packet_list_t *pl,
                                      RADIUS_PACKET *request);
#include "rad_packet.h"


class rad_server
{
public:
	rad_server(string name) : name_(name), failed_time_(0)
	{
		server_list.push_back(this);
	}

	void handle_recv_packet(RADIUS_PACKET *reply, rad_packet_ptr packet)
	{
        if (rad_verify(reply, packet->request_, secret_.c_str()) < 0)
        {
            cout << "rad_verify" << endl;
			rad_free(&reply);
            return;
        }
		remove_packet(packet);
        /*
         *	If this fails, we're out of memory.
         */
        if (rad_decode(reply, packet->request_, secret_.c_str()) != 0)
        {
            cout << "rad_decode" << endl;
			rad_free(&reply);
            return;
        }        
		cout << name_ << " receive reply code " << reply->code << endl;
		packet->cancel_timer();
		packet->parse_reply(reply);
		rad_free(&reply);
	}
	
	int send_packet(rad_packet_ptr &packet)
	{
		packet->server_ = this;
		packet->tried_servers_.insert(this);
		packet->request_->src_ipaddr.af = ipaddr_.af;    //will be used to select local socket(ipv4 or v6)
		packet->request_->dst_ipaddr = ipaddr_;
		packet->request_->id = -1;		

		int i, rcode;
			//assert(packet->reply_ == NULL);
		rcode = fr_packet_list_id_alloc_by_socket(fr_pl, packet->request_);
		if (rcode == 0)
		{
			cout << "error alloc packet id" << endl;
			return 0;
		}
		//assert(radclient->request->id != -1);
		//assert(radclient->request->data == NULL);
		for (i = 0; i < 4; i++)
		{
			((uint32_t *) packet->request_->vector)[i] = fr_rand();
		}
		
		packet->start_timer();
		packet_list[&packet->request_] = packet;
		if (!fr_packet_list_insert(fr_pl, &packet->request_))
		{
			assert(0 == 1);
		}		
		real_send(packet);
		return 1;
	}	

	void real_send(rad_packet_ptr &packet)
	{		
		packet->tries_++;
		cout << name_ << " send packet for " << packet->tries_ << endl;
		//if (packet->request_->dst_ipaddr.af == AF_INET6)   //test use
		{
			if (rad_send(packet->request_, NULL, secret_.c_str()) < 0)
			{
				cout << "[radius send]Failed to send packet for ID" << endl;
			}
		}
	}
	void failed_send()
	{
		cout << name_ << " failed " << endl;
		failed_time_++;
	}
	bool is_ipv4()
	{
		return ipaddr_.af == AF_INET;
	}
	static int rad_init()
	{
		//const char *radius_dir = RADDBDIR;
		int force_af = AF_UNSPEC;
		int sockfd;
		static fr_ipaddr_t client_ipaddr;
		int client_port;
		string common_key;
		rad_server *server;
		config_file *cf = config_file::instance;

		if (cf->contain("radius_shared_key"))
			common_key = cf->get_value("radius_shared_key");
		else
		{
			cout << "No radius server shared key specified in config file." << endl;
			return 0;
		}

		if (cf->contain("radius_server_ipv4"))		
		{
			server = new rad_server("main_ipv4_server");
			server->secret_ = common_key;
			if (1)
			{
				server->ipaddr_.af = AF_INET;
				if (ip_hton(cf->get_value("radius_server_ipv4").c_str(), force_af, &server->ipaddr_) < 0)
				{
					cout << "[radius client init]Failed to find IPv4 address for host" << endl;
					return 0;
				}
			}
		}
		if (cf->contain("radius_server_ipv6"))		
		{
			server = new rad_server("main_ipv6_server");
			server->secret_ = common_key;
			if (1)
			{
				server->ipaddr_.af = AF_INET6;
				if (inet_pton(AF_INET6, cf->get_value("radius_server_ipv6").c_str(), &server->ipaddr_.ipaddr) <= 0)
				{
					cout << "[radius client init]Failed to find IPv6 address for host" << endl;
					return 0;
				}
			}
		}
		if (cf->contain("radius_reserve_server_ipv4"))		
		{
			server = new rad_server("backup_ipv4_server");
			server->secret_ = common_key;
			if (1)
			{
				server->ipaddr_.af = AF_INET;
				if (ip_hton(cf->get_value("radius_reserve_server_ipv4").c_str(), force_af, &server->ipaddr_) < 0)
				{
					cout << "[radius client init]Failed to find IPv4 address for host" << endl;
					return 0;
				}
			}
		}
		if (cf->contain("radius_reserve_server_ipv6"))		
		{
			server = new rad_server("backup_ipv6_server");
			server->secret_ = common_key;
			if (1)
			{
				server->ipaddr_.af = AF_INET6;
				if (inet_pton(AF_INET6, cf->get_value("radius_reserve_server_ipv6").c_str(), &server->ipaddr_.ipaddr) <= 0)
				{
					cout << "[radius client init]Failed to find IPv6 address for host" << endl;
					return 0;
				}
			}
		}

		fr_pl = fr_packet_list_create(1);
		if (!fr_pl)
		{
			cout << "[radius client init]Out of memory";
			return 0;
		}

		memset(&client_ipaddr, 0, sizeof(client_ipaddr));
		client_ipaddr.af = AF_INET;
		client_port = 0;
		for (int i = 0; i < 10; i++)
		{
			sockfd = fr_socket(&client_ipaddr, client_port);
			if (sockfd < 0)
			{
				cout << "[radius client init]IPV4 socket" << endl;
				return 0;
			}
			if (!fr_packet_list_socket_add(fr_pl, sockfd))
			{
				cout << "[radius client init]Out of memory" << endl;
				return 0;
			}
		}
		client_ipaddr.af = AF_INET6;
		for (int i = 0; i < 10; i++)
		{
			sockfd = fr_socket(&client_ipaddr, client_port);
			if (sockfd < 0)
			{
				cout << "[radius client init]IPV6 socket" << endl;
				return 0;
			}
			if (!fr_packet_list_socket_add(fr_pl, sockfd))
			{
				cout << "[radius client init]Out of memory" << endl;
				return 0;
			}
		}
		/* Create packet receiving thread */
		boost::thread t(boost::bind(&rad_server::recv_thread));

		return 1;
	}
	static void rad_test(rad_packet_ptr p = rad_packet_ptr())
	{
		if (!p.get())
		{
			rad_callback_t call_back = boost::bind(&rad_server::test_result, _1);
			p = rad_packet_ptr(new rad_packet(rad_packet::PACKET_ACCOUNT, call_back));
		}

		if (p->tried_servers_.size() == 0)
		{
			p->set_test();
			p->set_try_servers(1);
			p->set_try_times(1);
		}
		if (rad_server *s = rad_server::get_best_server(p->tried_servers_))
			s->send_packet(p);
	}
	static void test_result(rad_packet_ptr &p)
	{
		config_file *cf = config_file::instance;
		if (p->reply_contain("Reply-Message"))
		{
			if (p->server_->is_ipv4())
				cf->config_vp["local_ipv4_from_remote"] = p->reply_value("Reply-Message");
			else
				cf->config_vp["local_ipv6_from_remote"] = p->reply_value("Reply-Message");
		}
		rad_test(p);
	}
	static void recv_thread()
	{
		fd_set		set;
		RADIUS_PACKET	*reply;
		volatile int max_fd;
		int result;

		while (1)
		{
			FD_ZERO(&set);
			max_fd = fr_packet_list_fd_set(fr_pl, &set);
			if (max_fd < 0) 
			{
				cout << "no socket listen on!" << endl;
				sleep(5);
				continue;
			}
			result = select(max_fd, &set, NULL, NULL, NULL);
			if (result <= 0)
			{
				cout << "select error!" << endl;
				sleep(5);
				continue;
			}
			/*
			 *	Look for the packet.
			 */
			reply = fr_packet_list_recv(fr_pl, &set);
			if (!reply)
			{
				cout << "receive bad packet" << endl;
				continue;
			}
			io_service_.post(boost::bind(&rad_server::on_recv_packet, reply));
		}
	}
	static void on_recv_packet(RADIUS_PACKET *reply)
	{
		RADIUS_PACKET **request_p;
        /*
         *	udpfromto issues.  We may have bound to "*",
         *	and we want to find the replies that are sent to
         *	(say) 127.0.0.1.
         */
        reply->dst_ipaddr.af = reply->src_ipaddr.af ;

        request_p = fr_packet_list_find_byreply(fr_pl, reply);
        if (!request_p)
        {
            cout << "[radius receive]received response to request we did not send." << endl;
			rad_free(&reply);
            return;
        }
		if (packet_list.find(request_p) == packet_list.end())
		{
            cout << "[radius receive]could not find server sent the packet" << endl;
			rad_free(&reply);
            return;
		}
		rad_server *server = packet_list[request_p]->server_;
		server->handle_recv_packet(reply, packet_list[request_p]);
	}
	// rad_packet should not live long after this call unless resend the same packet using different rad_server
	static void remove_packet(rad_packet_ptr &packet)
	{
		packet_list.erase(&packet->request_);
		fr_packet_list_yank(fr_pl, packet->request_);
		packet->tries_ = 0;
		if (!packet.get() || !packet->request_ ||
		(packet->request_->id < 0))
		{
			return;
		}
		/*
		 *	One more unused RADIUS ID.
		 */
		fr_packet_list_id_free(fr_pl, packet->request_);

		/*
		 *	If we've already sent a packet, free up the old one,
		 *	and ensure that the next packet has a unique
		 *	authentication vector.
		 */
		if (packet->request_->data)
		{
			free(packet->request_->data);
			packet->request_->data = NULL;
		}
		//if (packet->reply_) rad_free(&packet->reply_);
	}
	// find least failed time server
	static class rad_server *get_best_server(set <class rad_server *> & tried_servers)
	{
		list<class rad_server *>::iterator chosen_it = server_list.end();
		for (list<class rad_server *>::iterator it = server_list.begin(); it != server_list.end(); it++)
		{
			if (tried_servers.find(*it) != tried_servers.end()) continue;
			if (chosen_it == server_list.end()) chosen_it = it;
			if ((*it)->failed_time_ < (*chosen_it)->failed_time_)
				chosen_it = it;
		}	
		if (chosen_it == server_list.end()) return NULL;
		return *chosen_it;
	}
	static class rad_server *get_best_server()
	{
		if (server_list.empty()) return NULL;
		list<class rad_server *>::iterator chosen_it = server_list.begin();
		for (list<class rad_server *>::iterator it = server_list.begin(); it != server_list.end(); it++)
		{
			if ((*it)->failed_time_ < (*chosen_it)->failed_time_)
				chosen_it = it;
		}	
		return *chosen_it;
	}
	static list<class rad_server *> server_list;
private:
	static map<void *, rad_packet_ptr> packet_list;
	static fr_packet_list_t *fr_pl;
	static boost::asio::io_service& io_service_;	

	string name_;
	int failed_time_;	// once a packet is sent and doesn't get reply , this value++
	string secret_;
	fr_ipaddr_t ipaddr_;
};























#endif